FrontProxy
Definitions are not loaded to the main context
An low-level optional abstraction for managing and manipulating graphical or any dynamic frontend instances efficiently and process them in batch. The idea is to use proxy objects as lightweight references to groups of instances (like rectangles, circles combined with other primitives) in a larger scene and minimize the data transfer and symbols usage.
All properties of proxies are stored in linear auto-sizable buffers, which allows JIT-enabled processing and easy sync with a frontend (single transaction).
Needs
To load this library use any context that differs from Global
Needs["FrontProxy`" -> "f`"] // Quiet
Factory
Creates a new proxy type
f`Proxy[{properties__}, body_] _FrontProxy`Reference
or if you want to specify mutable properties explicitly
f`Proxy[{properties__}, body_, {mutable__}] _FrontProxy`Reference
For example to make proxy for many Disks with controllable opacity, color and position
disk = f`Proxy[{pos, c}, {
Opacity[c], RGBColor[With[{h=c}, {h, 1-h, 0.}]],
Disk[pos, 0.1]
}];
Proxy factory will automatically wrap all mentioned variables pos
and c
linear buffer parts wrapped with Offload
Constructor
To construct an single instance
f`AddTo[_FrontProxy`Reference, {args__}] _Integer
To construct multiple instances use
f`AddTo[_FrontProxy`Reference, {args__}..] _List
as a result it returns a plain List
of integers corresponding to the internal Ids
. For example
ids = f`AddTo[disk, Sequence @@ Table[{RandomReal[{-1,1}, 2], RandomReal[{0,1}]}, {10}]];
Methods
The following methods can be applied
FullForm
f`FullForm[_FrontProxy`Reference, id_Integer]
f`FullForm[_FrontProxy`Reference, {id__Integer}]
Reveals the actual expressions. This method can be used to display an object. For example
Graphics[{f`FullForm[disk, ids]}, PlotRange->{{-1,1}, {-1,1}}]
Remove
Removes the given proxy or list of proxies by provided Ids (live)
f`Remove[_FrontProxyFunction, Id_Integer]
f`Remove[_FrontProxyFunction, {Id__Integer}]
Dispatch
Dispatches the changes made to the properties of proxies and effectively syncing all buffers
f`Dispatch[_FrontProxyFunction]
It is called usually when all calculations have been finished and an update is needed to see the changes. Behind the scenes it submits all buffers storing the properties of object to the frontend.
FrontProxyBuffer
Provides a read access to the slice of a given property of all proxies in a form if linear packed array
FrontProxyBuffer[_FrontProxyFunction, index_Integer] _List
where index
goes from the first property provided in Constructor to the last. There is a special case
FrontProxyBuffer[_FrontProxyFunction, -1] _List
which returns a boolean array standing for the validity of the property at the given position. Since proxies are dynamic and can be created or removed any time it might temporary lead to "holes" in buffers marked as False
in the list.
Each position in buffers does correspond to Id
returned by Constructor
FrontProxyBufferSet
Updates a given property buffer with a new array
FrontProxyBuffer[_FrontProxyFunction, index_Integer, new_List]
For example one can update disks
primitives in the following way
With[{
position = f`Buffer[disk, 1]
},
With[{velocity = Table[{a[[2]], -a[[1]]}, {a, position}]},
f`BufferSet[disk, 1, position - 0.1 velocity];
f`Dispatch[disk];
];
];
It will cause the rotation of all disks
Tips
If you dynamically add proxies to the scene. Call Dispatch
before submitting it to a scene. This will make sure, that the buffer size is up-to date on the frontend as well.
For fast animations with many proxies involved turn off transition interpolation globally on Graphics using an option TransitionType
set to None
.
For processing many proxies use pure functions with Map
, Table
or MapThread
and etc. Multiple passes using less complicated function cost less, than a single pass with one complex due to JIT.
Examples
Spherical attracting molecules
Here we will use Lennard-Jones potential to model a bunch of sphere-like molecules on 2D canvas aka Graphics
Fireworks
A crash test for the frontend system
(* Define the rectangle proxy with initial properties *)
rectangleProxy = f`Proxy[
{position, velocity, rotationAngle, lifeSpan},
Translate[
{Opacity[lifeSpan], RGBColor[With[{l = lifeSpan}, {l, 0, 1 - l}]], Rectangle[{-1, -1}, {1, 1}]},
position
]
];
(* Initialize variables *)
newProxies = {};
expiredProxies = {};
frameCounter = 1;
frameRate = 1;
lastUpdateTime = AbsoluteTime[];
sceneReference = FrontInstanceReference[];
(* Function to add new proxies at a given position *)
addProxyAtPosition[position_] := newProxies = {
newProxies,
f`AddTo[
rectangleProxy,
Sequence @@ Table[
{position, RandomReal[{0.99, 1.01}] {Cos[angle], Sin[angle]} // N, RandomReal[{0, 3.14}], 1.0},
{angle, 0., 2 Pi, 2 Pi / 12.0}
]
]
};
(* Frame update logic *)
Module[{},
EventHandler["frame", Function[Null,
With[{
positions = f`Buffer[rectangleProxy, 1],
velocities = f`Buffer[rectangleProxy, 2],
lifeSpans = f`Buffer[rectangleProxy, 4],
isValid = f`Buffer[rectangleProxy, -1]
},
(* Identify expired proxies for disposal *)
expiredProxies = MapThread[
If[#1 && #2 < 0.2, #3, Nothing] &,
{isValid, lifeSpans, Range[Length[lifeSpans]]}
];
(* Update positions and life spans *)
f`BufferSet[rectangleProxy, 1, positions + velocities];
f`BufferSet[rectangleProxy, 4, lifeSpans * 0.95];
];
(* Dispatch updates to proxies *)
f`Dispatch[rectangleProxy];
(* Remove expired proxies *)
If[Length[expiredProxies] > 0,
f`Remove[rectangleProxy, expiredProxies];
expiredProxies = {};
];
(* Submit new proxies *)
If[Length[newProxies] > 0,
FrontSubmit[f`FullForm[rectangleProxy, newProxies // Flatten], sceneReference];
newProxies = {};
];
(* Update FPS counter *)
With[{currentTime = AbsoluteTime[]},
If[currentTime - lastUpdateTime > 1.0,
frameRate = Round[(frameCounter + frameRate) / 2.0];
frameCounter = 1;
lastUpdateTime = currentTime;
,
frameCounter++;
];
];
]]
];
(* Create the graphics and event handlers *)
Graphics[
{
sceneReference,
{Directive[FontSize -> 20], Text[frameRate // Offload, {-80, -80}]},
AnimationFrameListener[frameCounter // Offload, "Event" -> "frame"],
EventHandler[
Null,
{"mousemove" -> addProxyAtPosition}
]
},
PlotRange -> {{-100, 100}, {-100, 100}},
TransitionType -> None
]